/*********************************************
/*  Agent.java 
/*  Sample Agent for Text-Based Adventure Game
/*  COMP3411 Artificial Intelligence
/*  UNSW Session 1, 2013
 */


/*********************************************
 * Briefly describe how your program works, including any 
 * algorithms and data structures employed, and explain any 
 * design decisions you made along the way.
 * 
 * The program first tries to explore the entire map and compile it's
 * own map. We represent the map as char[160][160]. While exploring
 * we try to remove as many trees/doors as possible but we never use 
 * dynamite when exploring.
 * 
 * Once the map is generated we call an a* search for the gold. In theory the a*
 * search takes into account the items we have and items it can pick up because the heuristic is
 * admissible. Every time a new item is picked up the search space should be restarted and we should
 * eventually have a solution. Unfortunately this did not work as planned and causes some problem when 
 * using dynamite in the search. 
 * 
 * We ran out of time to fix this so added some code that basically blows a wall at random and
 * checks if a solution exists else repeat with a different wall. This code will never be called 
 * if a* works. 
 * 
 * More information on a* is in GeniusSearch class
 * 
 * 
 * 
 * 
 */
import java.util.*;
import java.io.*;
import java.net.*;

public class Agent {

	public static final int TURNLEFT = -1;
	public static final int TURNRIGHT = 1;

	private boolean haveGold = false;
	private boolean haveAxe = false;
	private boolean haveKey = false;
	//private ArrayList<Dynamite> haveDynamites;
	private int numDynamites = 0;
	private boolean exploringMode = true;
	private boolean pathFound = false; 

	Map map = new Map();
	GeniusSearch aStarSearch = new GeniusSearch();

	Stack<Position> toBeExploredPositions = new Stack<Position>();
	Position initialAgentPosition = new Position((map.Top + map.Bottom)/2,
			(map.Left + map.Right)/2);
	
	/*
	public Agent(){
		this.haveDynamites = new ArrayList<Dynamite>();
	}
	*/

	//1 - up
	//0 - left
	//2 - right
	//3 down
	public int dir = Map.UP;//this is absolute direction in the actual map 

	/*
	 * very simple, walk straight until you can't walk any further then turn left.
	 */
	public void initialisation(){
		toBeExploredPositions.add(initialAgentPosition);
		//initialAgentPosition.addedToStack = true;
	}

	//two parts, basically firstly visit all possible positions using depth first
	//once that done start planning what to do next
	//if we have the gold already then don't need to continue exploring anymore.
	public void decideWhereToGo(){
		int agentPositioni = (map.Top + map.Bottom)/2;
		int agentPositionj = (map.Left + map.Right)/2;
		Position currentPosition = new Position(agentPositioni, agentPositionj);

		if (map.getContentAtPosition(currentPosition)=='g'){
			haveGold = true;
		} else if (map.getContentAtPosition(currentPosition)=='a'){
			haveAxe = true;
		} else if (map.getContentAtPosition(currentPosition)=='k'){
			haveKey = true;
		} else if (map.getContentAtPosition(currentPosition)=='d'){

			this.numDynamites++;
		}
		
		this.aStarSearch.setCurrentPosition(agentPositioni, agentPositionj);
		//basically hack, this says that the position has been visited.
		//do not visit it again
		this.map.setPosition(agentPositioni, agentPositionj, '&');

		if (this.aStarSearch.isAtCurrentGoal()){

			/*
			 * if we have the gold then simply go home
			 */
			if (haveGold){
				this.aStarSearch.setCurrentGoal(GeniusSearch.homei, GeniusSearch.homej);
				this.aStarSearch.searchAgain();
				/*
				 * otherwise explore the map
				 */
			}else if (toBeExploredPositions.size() > 0){
				Position currentTarget = toBeExploredPositions.pop();

				this.aStarSearch.setCurrentGoal(currentTarget.i, currentTarget.j);
				this.aStarSearch.searchAgain();

				ArrayList<Position> adjacentPositions = new ArrayList<Position>();
				adjacentPositions.add(new Position(currentTarget.i-1, currentTarget.j));
				adjacentPositions.add(new Position(currentTarget.i+1, currentTarget.j));
				adjacentPositions.add(new Position(currentTarget.i, currentTarget.j-1));
				adjacentPositions.add(new Position(currentTarget.i, currentTarget.j+1));

				for (Position p : adjacentPositions){
					char content = map.getContentAtPosition(p);
					if (content != '&'){
						if (content == ' ' || content == 'a' || 
							content == 'k' || content == 'd' || content == 'g'){
							toBeExploredPositions.push(p);
						}
					}
				}
			/*
			 * if have key then take out all the doors and explore 
			 */
			} else if (haveKey && this.map.findGoal('-') != null){
				Position door = this.map.findGoal('-');
				if (door != null){
					toBeExploredPositions.push(door);
				}
			/*
			 * if have axe then take out all the trees then explore
			 */
			} else if (haveAxe && this.map.findGoal('T') != null){
				Position tree = this.map.findGoal('T');
				if (tree != null){
					toBeExploredPositions.push(tree);
				}
			/*
			 * check if we have enough items to finish the game	
			 */
			} else if (this.map.findGoal('g') != null){
				
				Position gold = this.map.findGoal('g') ;
				//toBeExploredPositions.push(gold);
				this.aStarSearch.setCurrentGoal(gold.i, gold.j);
				this.aStarSearch.searchAgain();
				
				exploringMode = false;
			}
			/*
			 * so we don't have enough items, and we can't use axe or key to go further.
			 * In which case we need to blow wall off. Pick a wall such that killing it
			 * will ensure that we will find enough dynamites to blow the remaining walls
			 * to the gold
			 * 
			 * This code should never be called if a* search works correctly
			 */
		} else if (pathFound == false){
			ArrayList<Position> allWalls = map.findAllGoals('*');

			for (Position wall : allWalls){
				Map mapWithThatWallRemoved = new Map (this.map);
				mapWithThatWallRemoved.getMyMap()[wall.i][wall.j] = ' ';
				
				int numberOfWallsToGold = mapWithThatWallRemoved.shortestDistanceToGold() - 1;
				int possibleDynamites = 0;
				
				ArrayList<Position> allDynamitePositions = mapWithThatWallRemoved.findAllGoals('d');
				
				for (Position dynamitePosition : allDynamitePositions){
					
					GeniusSearch tempSearch = new GeniusSearch();
					tempSearch.setCurrentPosition(agentPositioni, agentPositionj);
					tempSearch.setCurrentGoal(dynamitePosition.i, dynamitePosition.j);
					
					if (tempSearch.moveSearch
							(mapWithThatWallRemoved, dir, haveKey, haveAxe, numDynamites - 1) != false){
						possibleDynamites ++;
					}
				}
				
				/*
				 * blowing this wall ensures that we will find enough dynamites to 
				 * blow the rest of the wall to gold
				 */
				if (possibleDynamites + numDynamites - 1  >= numberOfWallsToGold){
					this.aStarSearch.setCurrentGoal(wall.i, wall.j);
					this.aStarSearch.searchAgain();
				}
			}
		}
	}

	public char get_action( char view[][] ) {

		// REPLACE THIS CODE WITH AI TO CHOOSE ACTION
		int ch=0;

		System.out.print("Enter Action(s): ");

		ch = aStarSearch.returnMovesMadeToGetToGold();

		switch( ch ) { // if character is a valid action, return it
		case 'F': if(dir == Map.UP) {
			map.Top--;
			map.Bottom--;
		} else if (dir == Map.DOWN) {
			map.Top++;
			map.Bottom++;
		} else if (dir == Map.LEFT) {
			map.Left--;
			map.Right--;
		} else {
			map.Left++;
			map.Right++;
		}
		break;
		case 'f': if(dir == Map.UP) {
			map.Top--;
			map.Bottom--;
		} else if (dir == Map.DOWN) {
			map.Top++;
			map.Bottom++;
		} else if (dir == Map.LEFT) {
			map.Left--;
			map.Right--;
		} else {
			map.Left++;
			map.Right++;
		}
		break;

		case 'L': setDirection(TURNLEFT);
		break;
		case 'R': setDirection(TURNRIGHT);
		break;
		case 'C': 
		break;
		case 'O':
		break;
		case 'B': numDynamites--;
		break;
		case 'l': setDirection(TURNLEFT);
		break;
		case 'r': setDirection(TURNRIGHT);
		break;
		case 'c':
		break;
		case 'o':
		break;
		case 'b': numDynamites--;
		break;
		}
		return((char) ch );
	}

	void setDirection(int a) {
		dir = dir + a;
		if(dir<0) {
			dir = Map.DOWN;
		} else if (dir > 3) {
			dir = Map.LEFT;
		}
	}

	void print_view( char view[][] )
	{
		int i,j;

		System.out.println("\n+-----+");
		for( i=0; i < 5; i++ ) {
			System.out.print("|");
			for( j=0; j < 5; j++ ) {
				if(( i == 2 )&&( j == 2 )) {
					System.out.print('^');
				}
				else {
					System.out.print( view[i][j] );
				}
			}
			System.out.println("|");
		}
		System.out.println("+-----+");
	}

	public static void main( String[] args )
	{
		InputStream in  = null;
		OutputStream out= null;
		Socket socket   = null;
		Agent  agent    = new Agent();
		char   view[][] = new char[5][5];
		char   action   = 'F';
		int port;
		int ch;
		int i,j;

		if( args.length < 2 ) {
			System.out.println("Usage: java Agent -p <port>\n");
			System.exit(-1);
		}

		port = Integer.parseInt( args[1] );

		try { // open socket to Game Engine
			socket = new Socket( "localhost", port );
			in  = socket.getInputStream();
			out = socket.getOutputStream();
		}
		catch( IOException e ) {
			System.out.println("Could not bind to port: "+port);
			System.exit(-1);
		}

		agent.initialisation();
		try { // scan 5-by-5 window around current location
			while( true ) {
				for( i=0; i < 5; i++ ) {
					for( j=0; j < 5; j++ ) {
						if( !(( i == 2 )&&( j == 2 ))) {
							ch = in.read();
							if( ch == -1 ) {
								System.exit(-1);
							}
							view[i][j] = (char) ch;
						}
					}
				}
			//	agent.print_view( view ); // COMMENT THIS OUT BEFORE SUBMISSION
				agent.map.updateMap(view, agent.dir);
				agent.decideWhereToGo();
				
				if (agent.exploringMode){
					agent.pathFound = agent.aStarSearch.moveSearch(agent.map, agent.dir,
											agent.haveKey, agent.haveAxe, 0);
				} else {
					agent.pathFound = agent.aStarSearch.moveSearch(agent.map, agent.dir,
							agent.haveKey, agent.haveAxe, agent.numDynamites);
				}
				
				action = agent.get_action( view );
				//agent.map.printMap(agent.dir);
				System.out.println(action);
				out.write( action );
			}
		}
		catch( IOException e ) {
			System.out.println("Lost connection to port: "+ port );
			System.exit(-1);
		}
		finally {
			try {
				socket.close();
			}
			catch( IOException e ) {}
		}
	}
}
